{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# FLASK中的数据库建模\r\n",
    "\r\n",
    "在本课中，我们将学习如何与数据库交互。今天，我们有两种类型的竞争数据库系统:\r\n",
    "\r\n",
    "1. 关系型数据库\r\n",
    "2. 非关系型数据库或 NoSQL 数据库\r\n",
    "\r\n",
    "关系数据库一直是 web 应用的传统方式。许多像 Facebook 这样的网络大玩家仍在使用它。关系数据库将数据存储在表和列中，并使用外键在一个或多个表之间建立关系。关系数据库还支持事务，这意味着您可以执行一组需要原子化(atomic)的 SQL 语句。所谓原子性，我指的是事务中全部的语句要么全部成功执行或者根本没有执行(补:大家像原子弹(Atomic bomb)一样绑定在一起,不成功则成仁)。\r\n",
    "\r\n",
    "近年来，NoSQL 数据库越来越受欢迎。NoSQL 数据库不在表和列中存储数据，而是使用文档存储、键值存储、图形等结构。大多数 NoSQL 也不支持事务处理，但它们确实提供了大量的速度。\r\n",
    "\r\n",
    "与 NoSQL 数据库相比，关系数据库已经非常成熟。他们已经证明了自己在许多行业中是可靠和安全的。因此，本课余下的部分将专门讨论如何使用 Flask 来使用关系数据库。这并不意味着 NoSQL 数据库毫无用处。事实上，在某些情况下 NoSQL 数据库比关系数据库数据库更有意义，但目前，我们的讨论仅限于关系数据库。\r\n",
    "\r\n",
    "## SQLAlchemy 和 flask-SQLAlchemy\r\n",
    "\r\n",
    "SQLAlchemy 是 Python 中处理关系数据库的事实上的框架。它是由迈克 · 拜耳于2005年创建的。SQLAlchemy 支持 MySQL、 PostgreSQL、 Oracle、 MS-SQL、 SQLite 等数据库。\r\n",
    "\r\n",
    "SQLAlchemy 附带了一个强大的 ORM (对象关系映射器) ，它允许我们使用面向对象的代码处理各种数据库，而不用编写原始 SQL。当然，我们不一定要以哪种方式使用 ORM，如果需要的话，我们也可以使用 SQL。\r\n",
    "\r\n",
    "Flask-SQLAlchemy 是一个集成 SQLAlchemy 框架和 Flask 的扩展。除此之外，它还提供了一些帮助器方法(helper methods)，使 SQLAlchemy 的工作变得更加容易。使用以下命令安装 Flask-SQLAlchemy 及其依赖项:\r\n",
    "\r\n",
    "`install flask-sqlalchemy`\r\n",
    "\r\n",
    "使用 Flask-SQLAlchemy 从 flask_SQLAlchemy 包中导入 SQLAlchemy 类，并通过向其传递应用程序实例来实例化 SQLAlchemy 对象。打开 main2.py 文件并修改如下(突出显示更改) :\r\n",
    "\r\n",
    "```python\r\n",
    "#...\r\n",
    "from forms import ContactForm\r\n",
    "from flask_sqlalchemy import SQLAlchemy # 引入\r\n",
    "\r\n",
    "app = Flask(__name__)\r\n",
    "app.debug = True\r\n",
    "app.config['SECRET_KEY'] = 'a really really really really long secret key'\r\n",
    "\r\n",
    "manager = Manager(app)\r\n",
    "db = SQLAlchemy(app)  # 用SQLAlchemy对app包裹后生成一个叫`db`的 实例(instance)\r\n",
    "\r\n",
    "class Faker(Command):\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "`SQLAlchemy` 实例 db 提供对所有 SQLAlchemy 函数的访问。\r\n",
    "\r\n",
    "接下来，我们需要告诉 Flask-SQLAlchemy 我们要用作 URI 的数据库的位置。数据库 URI 的格式如下:\r\n",
    "\r\n",
    "`dialect+driver://username:password@host:port/database`\r\n",
    "\r\n",
    "`dialect`方言指的是数据库的名称，如 mysql、 mssql、 postgresql 等。\r\n",
    "\r\n",
    "`driver`驱动程序指的是连接到数据库的 DBAPI。默认情况下，SQLAlchemy 只能在 SQLite 上工作，不需要任何额外的驱动程序。要使用其他数据库，您必须安装特定于数据库的 DBAPI 兼容驱动程序。\r\n",
    "\r\n",
    "那么什么是 DBAPI 呢？\r\n",
    "\r\n",
    "DBAPI 只是一个标准，它定义了一个通用的 pythonapi 来访问不同厂商的数据库。\r\n",
    "\r\n",
    "下表列出了一些数据库和它的 DBAPI 兼容驱动程序:\r\n",
    "\r\n",
    "| Database   | DBAPI Driver |\r\n",
    "| :--------- | :----------- |\r\n",
    "| MySQL      | PyMysql      |\r\n",
    "| PostgreSQL | Psycopg 2    |\r\n",
    "| MS-SQL     | pyodbc       |\r\n",
    "| Oracle     | cx_Oracle    |\r\n",
    "\r\n",
    "`username:password`用户名和密码是可选的，如果指定它将用于登录到数据库。\r\n",
    "\r\n",
    "`host`主机指向数据库服务器的位置。`port`端口是可选的数据库服务器端口。`database`数据库指向数据库的名称。\r\n",
    "\r\n",
    "下面是一些流行数据库的数据库 uri 示例:\r\n",
    "\r\n",
    "```python\r\n",
    "# database URI for MySQL using PyMysql driver\r\n",
    "'mysql+pymysql://root:pass@localhost/my_db'  \r\n",
    "\r\n",
    "# database URI for PostgreSQL using psycopg2 \r\n",
    "'postgresql+psycopg2://root:pass@localhost/my_db' \r\n",
    "\r\n",
    "# database URI for MS-SQL using pyodbc driver\r\n",
    "'mssql+pyodbc://root:pass@localhost/my_db' \r\n",
    "\r\n",
    " # database URI for Oracle using cx_Oracle driver\r\n",
    "'oracle+cx_oracle://root:pass@localhost/my_db'\r\n",
    "```\r\n",
    "\r\n",
    "SQLite 数据库的数据库 URI 的格式略有不同。因为 SQLite 是一个基于文件的数据库，不需要用户名和密码，所以在数据库 URI 中我们只指定数据库文件的路径名。\r\n",
    "\r\n",
    "```python\r\n",
    "# Unix/Mac 我们使用 4 个 斜杠'/'\r\n",
    "sqlite:////absolute/path/to/my_db.db  \r\n",
    "\r\n",
    "# Windows 我们使用 3 个 斜杠'/'\r\n",
    "sqlite:///c:/absolute/path/to/mysql.db\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "Flask-SQLAlchemy 使用 `SQLALCHEMY_DATABASE_URI` 配置键来指定数据库 URI。打开 main2.py 并添加 `SQLALCHEMY_DATABASE_URI` 配置键，如下所示(突出显示更改) :\r\n",
    "\r\n",
    "```python\r\n",
    "#...\r\n",
    "app = Flask(__name__)\r\n",
    "app.debug = True\r\n",
    "app.config['SECRET_KEY'] = 'a really really really really long secret key'\r\n",
    "app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:pass@localhost/flask_app_db'\r\n",
    "app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True\r\n",
    "manager = Manager(app)\r\n",
    "db = SQLAlchemy(app)\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "在本课程中，我们将使用 MySQL 数据库。因此，在进入下一部分之前，请确保您的计算机上已经安装了可以正常工作的 MySQL。\r\n",
    "\r\n",
    "\r\n",
    "## 创建模型\r\n",
    "\r\n",
    "Model 是一个 Python 类，它表示数据库表及其属性--映射到数据库表的列(字段)。模型类从 `db.Model` 继承并将列(字段)定义为 db.Column 的实例。\r\n",
    "\r\n",
    "打开 main2.py 文件，在`updating_session()`视图函数下面添加以下类:\r\n",
    "(补: 从结构上最好另建一个文件models.py专门放数据库的定义)\r\n",
    "\r\n",
    "flask_app/main2.py\r\n",
    "\r\n",
    "```python\r\n",
    "#...\r\n",
    "from flask_sqlalchemy import SQLAlchemy\r\n",
    "from datetime import datetime\r\n",
    "\r\n",
    "#...\r\n",
    "\r\n",
    "class Post(db.Model):\r\n",
    "    __tablename__ = 'posts'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    title = db.Column(db.String(255), nullable=False)\r\n",
    "    slug = db.Column(db.String(255), nullable=False)\r\n",
    "    content = db.Column(db.Text(), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)    \r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.title[:10])\r\n",
    "```\r\n",
    "\r\n",
    "这里我们创建了一个 Post 模型类，它包含5个类变量。除 `__tablename__` 以外的每个类变量都是 `db.Column` 的实例。`__tablename__` 是一个特殊的类变量，用于定义数据库表的名称。默认情况下，SQLAlchemy `不遵守`创建复数名称并在模型名称之后创建表名称的约定。如果不想依赖此行为(省略表名的约定方式)，可以使用 `__tablename__` 变量来显式地命名表。\r\n",
    "\r\n",
    "db.Column()构造函数的第一个参数是要创建的列的类型。SQLAlchemy 提供了大量的列类型，如果这还不够，您甚至可以定义自己的自定义类型。下表列出了 SQLAlchemy 提供的一些通用列类型及其在 Python 和 SQL 中的关联类型。\r\n",
    "\r\n",
    "| SQLAlchemy | Python            | SQL                 | 中文           |\r\n",
    "| :--------- | :---------------- | :------------------ |:------------- |\r\n",
    "| BigInteger | `int`             | BIGINT              |数字           |\r\n",
    "| Boolean    | `bool`            | BOOLEAN or SMALLINT |布尔           |\r\n",
    "| Date       | `datetime.date`   | DATE                |日期           |\r\n",
    "| DateTime   | `datetime.date`   | DATETIME            |日期时间           |\r\n",
    "| Integer    | `int`             | INTEGER             |小数字           |\r\n",
    "| Float      | `float`           | FLOAT or REAL       |浮点           |\r\n",
    "| Numeric    | `decimal.Decimal` | NUMERIC             |精度数字           |\r\n",
    "| Text       | `str`             | TEXT                |字符串           |\r\n",
    "\r\n",
    "我们还可以通过将列作为关键字参数传递给 db 来设置其他约束。列构造器。下表列出一些常用的限制:\r\n",
    "\r\n",
    "|  Constraint   |  Description                                                  |\t\t\t\t    \t描述                                                |\r\n",
    "| :---------- | :----------------------------------------------------------- |:-------------------------------------------------- |\r\n",
    "| nullable    | When set to `False` makes the column required. Its default value is `True`. |\t\t    \t当设置为 `False` 使列成为必需的。它的默认值是`True`.   |\r\n",
    "| default     | It provides a default value for the column.                  |\t\t它为列提供一个默认值                                |\r\n",
    "| index       | A boolean attribute. If set to `True` creates an indexed column. |一个布尔属性。如果设置为`True`创建一个索引列             |\r\n",
    "| onupdate    | It provides a default value for the column while updating a record. |\t\t\t    \t它在更新记录时为列提供默认值                        |\r\n",
    "| primary_key | A boolean attribute. If set to `True` marks the column as the primary key of the table. |   \t一个布尔属性。如果设置为`True` 将列标记为表的主键         |\r\n",
    "| unique      | A boolean attribute. If set to `True` each value in the column must be unique. |\t    \t一个布尔属性。如果设置为`True` 列中的每个值都必须是唯一的 |\r\n",
    "\r\n",
    "## 定义关系 Relationship\r\n",
    "\r\n",
    "在前面的部分中，我们创建了一个 Post 模型，它包含几个字段。然而，在现实世界中，模型类很少单独存在。大多数时候，它们通过各种关系(如一对一、一对多和多对多)与其他模型连接。\r\n",
    "\r\n",
    "让我们扩展一下博客网站的类比。一般来说，一篇博客文章属于一个类别和一个或多个标签。换句话说，类别和帖子之间有一对多的关系，帖子和标签之间有多对多的关系。下图演示了这种关系。\r\n",
    "\r\n",
    "![img](model_relationship-3e4a21d0-cca6-4b74-987e-5b5b63c3f571.png)\r\n",
    "\r\n",
    "打开 main2.py 并添加 Category 和 Tag 模型，如下所示(突出显示更改) :\r\n",
    "\r\n",
    "```python\r\n",
    "#...\r\n",
    "def updating_session():\r\n",
    "    #...\r\n",
    "    return res\r\n",
    "\r\n",
    "class Category(db.Model):\r\n",
    "    __tablename__ = 'categories'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False)\r\n",
    "    slug = db.Column(db.String(255), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(id, self.name)\r\n",
    "\r\n",
    "class Posts(db.Model):\r\n",
    "    # ...\r\n",
    "\r\n",
    "class Tag(db.Model):\r\n",
    "    __tablename__ = 'tags'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False)\r\n",
    "    slug = db.Column(db.String(255), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(id, self.name)\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "## 一对多的关系\r\n",
    "\r\n",
    "通过在子表上放置一个外键，可以创建一对多的关系。这是在使用数据库时遇到的最常见的关系类型。为了在 SQLAlchemy 中创建一对多的关系，我们做了以下操作:\r\n",
    "\r\n",
    "1. 创建一个新的 实例使用 子类中的约束\r\n",
    "2. 使用以下命令定义新属性 此属性将用于访问相关对象\r\n",
    "\r\n",
    "打开 main2.py 并修改 Post 和 Category 模型如下(突出显示更改) :\r\n",
    "\r\n",
    "flask_app/models.py\r\n",
    "\r\n",
    "#...\r\n",
    "class Category(db.Model):\r\n",
    "    # ...\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    posts = db.relationship('Post', backref='category')\r\n",
    "\r\n",
    "class Post(db.Model):\r\n",
    "    # ...\r\n",
    "    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)\r\n",
    "    category_id = db.Column(db.Integer(), db.ForeignKey('categories.id'))\r\n",
    "#...\r\n",
    "\r\n",
    "这里我们分别在 Category 和 Post 模型中添加了两个新的属性 posts 和 Category_id。\r\n",
    "\r\n",
    "`db.ForeignKey()` 接受在其上定义外键的列的名称。这里我们将 `categories.id` 传递给 `db.ForeignKey()` ，这意味着 `Post` 模型的 `category_id` 属性只能从 categories 表的 id 列获取值。\r\n",
    "\r\n",
    "接下来，我们在 `Category` 模型中使用 `db.relationship()` 指令定义 `posts` 属性。将 `db.relationship()` 用于添加到双向(bidirectional)关系。换句话说，它在模型类上添加一个属性来访问相关的对象。最简单地说，它至少接受一个位置参数，这个位置参数是关系的另一端的类的名称。\r\n",
    "\r\n",
    "```python\r\n",
    "class Category(db.Model):\r\n",
    "    # ...    \r\n",
    "    posts = db.relationship('Post')\r\n",
    "```\r\n",
    "\r\n",
    "现在，如果我们有一个 `Category` 对象(比如 `c` ) ，那么我们就可以访问它下面的所有 `post` 。如果您想从关系的另一端访问数据，即从 post 对象获取类别，该怎么办？这就是 `backref` 回溯的作用所在。所以这个代码是:\r\n",
    "\r\n",
    "` posts = db.relationship('Post', backref='category') `\r\n",
    "\r\n",
    "将 `category` 属性添加到 `Post` 对象。这意味着如果我们有一个 `Post` 对象(比如说 `p` ) ，那么我们就可以作为 `p.category` 访问它的类别 `category` 。\r\n",
    "\r\n",
    "Post 和 Category 对象上的 Category 和 posts 属性只是为了方便而存在，它们不是表中的实际列。\r\n",
    "\r\n",
    "请注意，不像表示外键的属性(必须在关系的许多方面定义)，您可以在关系的任何方面定义 db.relationship()。\r\n",
    "\r\n",
    "## 一对一的关系\r\n",
    "\r\n",
    "在 SQLAlchemy 中建立一对一关系几乎与一对多关系相同，唯一的区别是我们向 `db.relationship()` 指令传递了一个额外的参数 `uselist = False` (补充: uselist 即使用列表,代表多余1个,所以需要用列表list来表达这种复数关系, 这里设置为否, 就表示不需要列表, 所以是单数关系)。下面是一个例子:\r\n",
    "\r\n",
    "```python\r\n",
    "class Employee(db.Model):\r\n",
    "    __tablename__ = 'employees'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False)\r\n",
    "    designation = db.Column(db.String(255), nullable=False)\r\n",
    "    doj = db.Column(db.Date(), nullable=False)\r\n",
    "    dl = db.relationship('DriverLicense', backref='employee', uselist=False)\r\n",
    "\r\n",
    "class DriverLicense(db.Model):\r\n",
    "    __tablename__ = 'driverlicense'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    license_number = db.Column(db.String(255), nullable=False)\r\n",
    "    renewed_on = db.Column(db.Date(), nullable=False)\r\n",
    "    expiry_date = db.Column(db.Date(), nullable=False)\r\n",
    "    employee_id = db.Column(db.Integer(), db.ForeignKey('employees.id'))  # Foreign key\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "注意: 在这些类中，我们假设员工不能有多个驾照。所以雇员和驾驶执照的关系是一对一的。\r\n",
    "\r\n",
    "现在，如果我们有一个 `Employee` 对象 `e`，那么 `e.dl` 将返回一个 `DriverLicense` 对象。如果我们没有将 `uselist = False` 传递给 `db.relationship()` 指令，那么 `Employee` 和 `DriverLicense` 之间的关系将是一对多的，而 `e.dl` 将返回一个 `DriverLicense` 对象`列表`，而不是`单个`对象。`Uselist = False` 参数对 `DriverLicense` 对象的 `employee` 属性没有任何影响。像往常一样，它将返回一个对象。\r\n",
    "\r\n",
    "## 多对多关系\r\n",
    "\r\n",
    "多对多关系需要一个额外的表，即关联表:\r\n",
    "\r\n",
    "一篇博客文章通常与一个或多个标签相关联。类似地，标记也与一个或多个帖子相关联。因此，`post` 和 `tags` 之间存在多对多的关系。在标记表中添加引用 post id 的外键是不够的，因为标记可以包含一个或多个 `post`。\r\n",
    "\r\n",
    "解决方案是通过定义引用 post.id 和 tag.id 列的2个外键来创建一个名为关联表的新表。\r\n",
    "\r\n",
    "![img](many_to_many_relationship-aa0619ae-8ae8-4cfb-8a56-7e4eec8d6788.png)\r\n",
    "\r\n",
    "正如您在图中看到的，post 和标记之间的多对多关系实现为两个一对多关系。第一个一对多的关系是 `posts` 和 `post_tags` 表之间的关系，第二个一对多关系是 `tags` 表和 `post_tags` 表之间的关系。下面的代码演示如何在 SQLAlchemy 中创建多对多关系。打开 main2.py 文件并添加以下代码(突出显示更改)。\r\n",
    "\r\n",
    "```python\r\n",
    "# ...\r\n",
    "class Category(db.Model):\r\n",
    "    # ...\r\n",
    "        def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(id, self.name)\r\n",
    "\r\n",
    "post_tags = db.Table('post_tags', #1\r\n",
    "    db.Column('post_id', db.Integer, db.ForeignKey('posts.id')), #2 \r\n",
    "    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')) #3\r\n",
    ") #4\r\n",
    "\r\n",
    "class Post(db.Model):\r\n",
    "    # ...\r\n",
    "\r\n",
    "class Tag(db.Model):\r\n",
    "    # ...\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    posts = db.relationship('Post', secondary=post_tags, backref='tags') \r\n",
    "    #5 第18行中，我们通过把辅助参数 `secondary` 设置为 `post_tags` 来调用 `db.relationship()` 。(注, 同时我们还设置了 `backref` 回溯参数为 `tags`，容许反向引用）。\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "在第7-10行中，我们将关联表 `post_tags` 定义为 `db.Table()` 的对象。`db.Table()` 的第一个参数是表的名称，其他参数是由 `db.Column()` 的实例表示的列。与模型类相比，创建关联表的语法可能显得有点奇怪。这是因为关联表是使用 `SQLAlchemy Core` 创建的，这是 `SQLAlchemy` 的另一个方面。要了解更多关于 SQLAlchemy 的信息，请访问我们的 SQLAlchemy 教程(已翻译。\r\n",
    "\r\n",
    "接下来，我们必须告诉我们的模型类我们想要使用的关联表，这就是次要关键字参数 `secondary` 要担当的工作。在第18行中，我们通过把辅助参数 `secondary` 设置为 `post_tags` 来调用 `db.relationship()` 。(注, 同时我们还设置了 `backref` 回溯参数为 `tags`，容许反向引用）。虽然这里我们已经在 `Tag` 模型中定义了关系，但是我们同样也可以在 `Post` 模型中定义它。\r\n",
    "\r\n",
    "假设我们有一个 `Post` 对象 `p` ，那么我们可以将它的所有标签作为 `p.tags` 访问。类似地，给定一个 `Tag` 对象 `t`，我们可以访问它下面的所有 `post` 作为 `t.posts`。\r\n",
    "\r\n",
    "现在是时候创建我们的数据库和其中的表了。\r\n",
    "\r\n",
    "## 创建表格\r\n",
    "\r\n",
    "要跟随本课的其余部分，您应该有一个可以正常工作的 `MySQL` 安装。如果你没有，点击这里学习[如何安装 MySQL](https://overiq.com/installing-mysql-windows-linux-and-mac/)。\r\n",
    "\r\n",
    "回想一下，默认情况下 `SQLAlchemy` 只能在 `SQLite` 数据库上工作。为了使用其他数据库，我们必须安装一个与 `DBAPI` 兼容的驱动程序。当我们使用 MySQL 时，我们将安装 `PyMySql` 驱动程序。\r\n",
    "\r\n",
    "`pip install pymysql`\r\n",
    "\r\n",
    "登录 MySQL 服务器，使用以下命令创建一个名为 `flask_app_db` 的数据库:\r\n",
    "\r\n",
    "```\r\n",
    "(env) overiq@vm:~/flask_app$ mysql -u root -p\r\n",
    "mysql>\r\n",
    "mysql> CREATE DATABASE flask_app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\r\n",
    "Query OK, 1 row affected (0.26 sec)\r\n",
    "\r\n",
    "mysql> \\q\r\n",
    "Bye\r\n",
    "(env) overiq@vm:~/flask_app$\r\n",
    "```\r\n",
    "\r\n",
    "这个命令创建了完全支持 Unicode 的 `flask_app_db` 数据库。\r\n",
    "\r\n",
    "要从模型中创建必要的表，请调用 SQLAlchemy 对象(db)的 `create_all()` 方法。启动 Python shell 并输入以下命令:\r\n",
    "\r\n",
    "```\r\n",
    "(env) overiq@vm:~/flask_app$ python main2.py shell\r\n",
    ">>>\r\n",
    ">>> from main2 import db\r\n",
    ">>>\r\n",
    ">>> db.create_all()\r\n",
    ">>>\r\n",
    "```\r\n",
    "\r\n",
    "Create_all() 方法`只在数据库中不存在表时才创建表`。所以你可以安全地多次运行它。除此之外，Create_all() 方法不考虑在创建表时对模型所做的修改。这意味着一旦在数据库中创建了一个表，在修改其模型之后运行 Create_all() 方法将不会改变表模式。为了做到这一点，我们使用了 `Alembic` 这样的迁移工具。我们将学习如何在课程数据库迁移中[使用 Alembic 执行数据库迁移](https://overiq.com/flask-101/database-migrations-with-alembic/) 。\r\n",
    "\r\n",
    "要查看在 MySQL 服务器上创建的表并执行以下命令:\r\n",
    "\r\n",
    "```\r\n",
    "mysql>\r\n",
    "mysql> use flask_app_db\r\n",
    "Database changed\r\n",
    "mysql>\r\n",
    "mysql> show tables;\r\n",
    "+------------------------+\r\n",
    "| Tables_in_flask_app_db |\r\n",
    "+------------------------+\r\n",
    "| categories             |\r\n",
    "| post_tags              |\r\n",
    "| posts                  |\r\n",
    "| tags                   |\r\n",
    "+------------------------+\r\n",
    "4 rows in set (0.02 sec)\r\n",
    "\r\n",
    "mysql>\r\n",
    "```\r\n",
    "\r\n",
    "查看表的另一种方法是使用数据库管理工具，如 `HeidiSQL`。`HeidiSQL` 是一个跨平台的开源软件，用于管理 MySQL、 MS-SQL 和 PostgreSQL。它允许我们浏览数据、编辑数据、查看模式、修改表格等等，而无需编写一行 SQL 语句。你可以从这里下载[HeidiSQL](https://www.heidisql.com/download.php)。\r\n",
    "\r\n",
    "\r\n",
    "一旦安装完成, 在 HeidiSQL 中打开 flask_app_db 数据库，你就会看到一个表列表，如下所示:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/18/tables_in_HeidiSQL-39713bbe-9361-4ecb-a53a-84d8c1bf5623.png)\r\n",
    "\r\n",
    "数据库现在有4个表。表名即 `categories, posts and tags` 是直接从模型创建的，而表 `post_tags` 是一个关联表，它表示职位和标签模型之间的多对多关系。\r\n",
    "\r\n",
    "SQLAlchemy 类还定义了一个名为 `drop_all()` 的方法，用于从数据库中删除所有表。记住，`drop_all()` 不关心表是否包含任何数据。它会`立即删除所有的数据和表格`，所以要谨慎使用。\r\n",
    "\r\n",
    "现在我们已经将所有的表都放好了。让我们在它们中输入一些数据。\r\n",
    "\r\n"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}